use std::collections::HashMap;
use std::fs::DirBuilder;

use serde_json::value::{to_value, Value as SerdeJsonValue};
use std::error::Error;
use tera::try_get_value;
use tera::{Context, Result, Tera};

use model_graph_types::{
    generator::{CodeGenerator, CodeGeneratorOption, CodeLanguage},
    modeling::Value,
};

#[derive(Default)]
pub struct TeraEngine {}

impl TeraEngine {
    pub fn new() -> Self {
        Self {}
    }
}

#[async_trait::async_trait]
impl CodeGenerator for TeraEngine {
    async fn generate(
        &self,
        language: CodeLanguage,
        data_context: Value,
        template_dir: &str,
        files: &Vec<String>,
        dist_dir: &str,
        options: CodeGeneratorOption,
    ) -> anyhow::Result<()> {
        //
        let mut context = Context::from_value(
            data_context
                .try_into()
                .map_or(Err(anyhow::anyhow!("")), |o| Ok(o))?,
        )?;

        context.insert("username", &"Bob");
        context.insert("numbers", &vec![1, 2, 3]);
        context.insert("show_all", &false);
        context.insert("bio", &"<script>alert('pwnd');</script>");

        // A one off template
        Tera::one_off("hello", &Context::new(), true).unwrap();

        let templates: Tera = {
            let mut tera = match Tera::new(format!("{}/**/*", template_dir).as_str()) {
                Ok(t) => t,
                Err(e) => {
                    println!("Parsing error(s): {}", e);
                    ::std::process::exit(1);
                }
            };
            tera.autoescape_on(vec!["html", ".sql"]);
            tera.register_filter("do_nothing", do_nothing_filter);
            tera
        };

        //writer
        for file_name in files {
            std::fs::create_dir_all(dist_dir)?;

            //创建文件
            let mut write = std::fs::File::create(format!("{}{}", dist_dir, file_name))?;

            let result = templates.render_to(format!("{}", file_name).as_str(), &context, write);
            match result {
                Ok(s) => println!("{:?}", "成功"),
                Err(e) => {
                    println!("Error: {}", e);
                    let mut cause = e.source();
                    while let Some(e) = cause {
                        println!("Reason: {}", e);
                        cause = e.source();
                    }
                }
            };
        }
        Ok(())
    }

    async fn generate_string(
        &self,
        language: CodeLanguage,
        data_context: Value,
        template: &str,
        options: CodeGeneratorOption,
    ) -> anyhow::Result<String> {
        tracing::debug!("{:?}", data_context);
        //
        let context = Context::from_value(
            data_context
                .try_into()
                .map_or(Err(anyhow::anyhow!("")), |o| Ok(o))?,
        )?;

        let mut tera = Tera::new("templates/**/*").map_err(|err| anyhow::anyhow!("{}", err))?;
        tera.add_raw_template("generate_string", template)
            .map_err(|err| anyhow::anyhow!("{}", err))?;

        let rendered = tera
            .render("generate_string", &context)
            .map_err(|err| anyhow::anyhow!("{}", err))?;

        Ok(rendered)
    }

    async fn simple(&self, data_context: Value, template: &str) -> anyhow::Result<String> {
        tracing::debug!("{:?}", data_context);
        //
        let context = Context::from_value(
            data_context
                .try_into()
                .map_or(Err(anyhow::anyhow!("")), |o| Ok(o))?,
        )?;

        let mut tera = Tera::new("templates/**/*").map_err(|err| anyhow::anyhow!("{}", err))?;
        tera.add_raw_template("generate_string", template)
            .map_err(|err| anyhow::anyhow!("{}", err))?;

        let rendered = tera
            .render("generate_string", &context)
            .map_err(|err| anyhow::anyhow!("{}", err))?;

        Ok(rendered)
    }
}

pub fn do_nothing_filter(
    value: &SerdeJsonValue,
    _: &HashMap<String, SerdeJsonValue>,
) -> Result<SerdeJsonValue> {
    let s = try_get_value!("do_nothing_filter", "value", String, value);
    Ok(to_value(s).unwrap())
}
